Necessary imports:
In [1]:
%matplotlib inline
from robust_STN import *
We instantiate the nominal model and solve it using the default solver (Gurobi).
The data of the nominal model can be changed from within STN.__init__()
.
In [2]:
stn = STN()
stn.solve()
Out[2]:
Plot the nominal schedule:
In [3]:
stn.plot_schedule()
Attained objective:
In [4]:
stn.model.value
Out[4]:
A robust STN is instantiated with a reference to a particular nominal STN (e.g., the one created in the previous section).
In [5]:
rSTN = robust_STN(stn)
The module robust_STN.py provides two functions to generate appropriate uncertainty sets:
robust_STN.build_uncertainty_set_for_time_delay(units=(0,), tasks=(0,), delay=1, from_t=0, to_t=T)
robust_STN.build_uncertainty_set_for_unit_swap(from_unit=2, to_unit=1, tasks=(1,), from_t=0, to_t=T)
Below some examples of how they can be used. These are sufficient to reproduce the results in [1].
We first produce a robust schedule that is immune to possible time delays of the heater (unit=0), when performing heating (task=0), of at most 1 time step (delay=1), which can happen anytime in the scheduling horizon (from_t and to_t assigned default values)
In [6]:
rSTN.W = rSTN.build_uncertainty_set_for_time_delay(units=(0,), tasks=(0,), delay=1)
rSTN.solve()
Plot the robust schedule
In [7]:
rSTN.plot_schedule()
We can then simulate an uncertain event, and see how the schedule has to be adapted to accommodate it.
In [9]:
rSTN.simulate_uncertain_event(event=[0,0,0], column=1)
rSTN.simulate_uncertain_event(event=[0,0,5], column=1)
Events are indexed according to the decision they affect. The column
parameter is used to simulate appropriate delays, when they can be longer than 1 time step (column=2
would simulate a delay by two time steps). Check the implementation of robust_STN.simulate_uncertain_event()
for more details on how the parameters are to be understood.
In [10]:
rSTN.W = rSTN.build_uncertainty_set_for_time_delay(units=(2,), tasks=(1,3), delay=1)
rSTN.solve()
In [12]:
rSTN.plot_schedule()
rSTN.simulate_uncertain_event(event=[2,1,0], column=1)
rSTN.simulate_uncertain_event(event=[2,1,5], column=1)
We see that the optimizer does not schedule any reaction 3 on reactor 2. In the above plots we can also inspect how the schedule has to be adjusted when the delay does occur.
In [13]:
rSTN.W = rSTN.build_uncertainty_set_for_unit_swap(from_unit=1, to_unit=2, tasks=(2,), from_t=4)
rSTN.solve()
And wen can again check the resulting schedules (remember events are counted from from_t
):
In [14]:
rSTN.plot_schedule()
rSTN.simulate_uncertain_event(event=[1,2,4])
Observe how in the revised schedule, the core is maintained, but the batch sizes in the remainder of the schedule are changed (reduced) due to the unit swap to a unit which is smaller.
1) The code pertaining to the nominal model is within STN.py. It contains the data used in the STN.__init__() method, which now replicates the results from the original paper [2]. It also contains the functions used to construct the 'core' constraints.
2) To run this locally, you need cvxpy and gurobipy (academic lisenses for gurobi can be obtained)
3) Most of the increase in computation time from the nominal model is due to the computation of the recourse matrix. If you do not need recourse, or your problem doesn't have/allow the adjustment of the continuous variables when uncertain outcomes occur, you can use the RC provided in Corollary 2.4 in [1]; the resulting robust cuonterpart then is exactly as expensive as the nominal model.
4) The reported solve times on this notebook may be different from those in the paper; they were solved on a different machine (the results in thie notebook are based on the performance on a laptop).